#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "pagedir.h"
#include "process.h"
#include "filesys/filesys.h"
#include "filesys/file.h"
#include "vm/page.h"

#include "threads/vaddr.h"

void munmap(mapid_t mapping)
{

  if (mapping <= 0)
    return;

  struct list *map_list = &(thread_current()->mmap_file_list);
  struct list_elem *e = list_begin(map_list);

  for (e = list_begin(map_list); e != list_end(map_list); e = e)
  {
    struct map_item *item = list_entry(e, struct map_item, elem);

    if (item->map_id == mapping)
    {

      struct s_page_table_entry *s_pt_entry = item->s_pt_entry;
      file_seek(s_pt_entry->file, 0);

      if (pagedir_is_dirty(thread_current()->pagedir, s_pt_entry->upage))
      {
        uint32_t bytes_written = file_write_at(s_pt_entry->file, s_pt_entry->upage, s_pt_entry->read_bytes, s_pt_entry->offset);
      }

      s_page_table_entry_clear(s_pt_entry);

      e = list_remove(&item->elem);
      free(item);
    }
    else
    {
      e = list_next(e);
    }
  }
  thread_current()->map_id--;
}
bool add_to_mmap_file_list(struct s_page_table_entry *s_pt_entry)
{
  struct map_item *item = malloc(sizeof(struct map_item));
  if (item == NULL)
    return false;
  struct thread *t = thread_current();
  item->s_pt_entry = s_pt_entry;
  item->map_id = t->map_id;
  list_push_back(&t->mmap_file_list, &item->elem);
  return true;
}
void *
exit_special()
{
  thread_current()->exit_status = -1;
  thread_exit();
}
void *
check_pointer(const void *vaddr)
{
  /* 检查指针是否无效 */
  if (!is_user_vaddr(vaddr))
  {
    thread_exit_with_status(-1);
  }
  /* 检查页面是否无效 */
  // if (!pagedir_get_page(thread_current()->pagedir, vaddr))
  // {
  //   thread_exit_with_status(-1);
  // }
  // return ptr;
  return NULL;
}

static void syscall_handler(struct intr_frame *);

void syscall_init(void)
{
  intr_register_int(0x30, 3, INTR_ON, syscall_handler, "syscall");
}

void thread_exit_with_status(int status)
{
  thread_current()->exit_status = status;
  thread_exit();
}

/*check address and pages*/
// void check_ptr(const void *vaddr)
// {
//   /* check address */
//   if (!is_user_vaddr(vaddr))
//   {
//     thread_exit_with_status(-1);
//   }
//   /* check the page */
//   if (!pagedir_get_page(thread_current()->pagedir, vaddr))
//   {
//     thread_exit_with_status(-1);
//   }
// }

static void
syscall_handler(struct intr_frame *f UNUSED)
{
  check_pointer(f->esp);

  switch (*(int *)f->esp)
  {
  case SYS_HALT:
  {
    shutdown_power_off();
    break;
  }
  case SYS_EXIT:
  {
    int *temp = f->esp;
    check_pointer(temp + 1);
    thread_current()->exit_status = *(int *)(temp + 1);
    thread_exit();
    break;
  }
  case SYS_EXEC:
  {
    // printf("sys exec!\n");
    check_pointer(((int *)f->esp + 1));
    const char *buffer = (const char *)(*((int *)f->esp + 1));
    check_pointer(buffer);
    f->eax = process_execute(buffer);
    break;
    // release_file_lock();
  }
  case SYS_WAIT:
  {
    check_pointer((int *)f->esp + 1);
    int pid = *((int *)f->esp + 1);
    f->eax = process_wait(pid);
    break;
  }
  case SYS_CREATE:
  {
    // printf("sys create!\n");
    check_pointer(((int *)f->esp + 1));
    const char *buffer = (const char *)(*((int *)f->esp + 1));
    check_pointer(buffer);
    check_pointer(((int *)f->esp + 2));
    if (!buffer)
      exit_special();
    long size = *((int *)f->esp + 2);
    f->eax = filesys_create(buffer, size);
    break;
  }

  case SYS_REMOVE:
  {
    check_pointer(((int *)f->esp + 1));
    const char *buffer = (const char *)(*((int *)f->esp + 1));
    check_pointer(buffer);
    f->eax = filesys_remove(buffer);
    break;
  }
  case SYS_OPEN:
  {
    check_pointer(((int *)f->esp + 1));
    const char *buffer = (const char *)(*((int *)f->esp + 1));
    check_pointer(buffer);
    if (!buffer)
      exit_special();
    struct file *fil = filesys_open(buffer);
    if (!fil)
    {
      // exit_special();
      f->eax = -1;
    }
    else
    {
      for (int i = 3; i < MAXFILE; i++)
      {
        if (thread_current()->fd[i] == NULL)
        {
          f->eax = i;
          thread_current()->fd[i] = fil;
          break;
        }
      }
    }
    break;
  }
  case SYS_FILESIZE:
  {
    // printf("sys filesize!\n");
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
      exit_special();
    // acquire_file_lock();
    f->eax = file_length(thread_current()->fd[fd]);
    // release_file_lock();
    break;
  }
  case SYS_READ:
  {
    check_pointer((int *)f->esp);
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    check_pointer(((int *)f->esp + 2));
    void *buffer = (const void *)(*((int *)f->esp + 2));
    check_pointer(buffer);
    check_pointer(((int *)f->esp + 3));
    unsigned size = *((unsigned *)f->esp + 3);
    if (buffer + size == NULL || !is_user_vaddr(buffer + size))
    {
      exit_special();
    }
    struct s_page_table_entry *p = get_s_pt_entry(buffer);
    if (p != NULL && !p->writable)
      exit_special();
    // printf("%x", buffer - f->esp);
    if (p == NULL && buffer < f->esp)
      exit_special();
    acquire_lock();
    void *buffer_rd = pg_round_down(buffer);
    void *buffer_page;
    unsigned readsize = (unsigned)(buffer_rd + PGSIZE - (void *)buffer);
    unsigned bytes_read = 0;
    bool read = true;
    int count = 0, result = 0;
    // printf("%x %x\n", buffer, f->esp);
    for (buffer_page = buffer_rd; buffer_page <= (void *)buffer + size; buffer_page += PGSIZE)
    {
      struct s_page_table_entry *spte = get_s_pt_entry(buffer_page);
      if (!spte)
      {
        grow_stack_one_page(buffer_page);
      }
    }

    if (fd == 0)
    {
      int i = size;
      while (i)
      {
        *(char *)buffer = input_getc();
        buffer = buffer + 1;
        i--;
      }
    }
    else
    {
      if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
      {
        release_lock();
        exit_special();
      }
      if (size <= readsize)
      {

        size = file_read(thread_current()->fd[fd], (void *)buffer, size);
      }
      else
      {
        while (read)
        {
          bytes_read = file_read(thread_current()->fd[fd], (void *)buffer, readsize);
          if (bytes_read != readsize)
          {
            read = false;
          }
          size -= bytes_read;
          if (size == 0)
          {
            read = false;
          }
          else
          {
            buffer += bytes_read;
            if (size >= PGSIZE)
              readsize = PGSIZE;
            else
              readsize = size;
          }
          result += bytes_read;
        }
        size = result;
      }
    }
    release_lock();
    f->eax = size;
    break;
  }
  case SYS_WRITE:
  {
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    check_pointer(((int *)f->esp + 2));
    const char *buffer = (const char *)(*((int *)f->esp + 2));
    check_pointer(buffer);
    check_pointer(((int *)f->esp + 3));
    unsigned size = *((unsigned *)f->esp + 3);
    // printf("%d %s %d\n", fd, buffer, size);
    acquire_lock();
    int act = 0;
    if (fd == 1)
    {
      // acquire_lock();
      putbuf(buffer, size);
      f->eax = size;
      release_lock();
      break;
    }
    else
    {
      if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
        exit_special();
      ASSERT(size >= 0);
      // acquire_lock();
      act = file_write(thread_current()->fd[fd], buffer, size);
      if (act == 0)
      {
        file_seek(thread_current()->fd[fd], 0);
        act = file_write(thread_current()->fd[fd], buffer, size);
      }
      // printf("%d %d\n", size, (int)sizeof(buffer));
      release_lock();
    }
    f->eax = act;
    break;
  }

  case SYS_SEEK:
  {
    // printf("sys seek!\n");
    uint32_t *esp = f->esp;
    check_pointer(esp + 1);
    check_pointer(esp + 2);
    int fd = *(esp + 1);
    if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
    {
      f->eax = 0;
      break;
    }
    acquire_lock();
    file_seek(thread_current()->fd[fd], *(esp + 2));
    release_lock();
    break;
  }
  case SYS_TELL:
  {
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
    {
      f->eax = 0;
      break;
    }
    acquire_lock();
    f->eax = file_tell(thread_current()->fd[fd]);
    release_lock();
    // printf("sys tell!\n");
    break;
  }
  case SYS_CLOSE:
  {
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    if (fd < 0 || fd >= MAXFILE || thread_current()->fd[fd] == NULL)
    {
      f->eax = 0;
      break;
    }
    // acquire_file_lock();
    struct list *s_page_table = &thread_current()->s_page_table;
    struct list_elem *e;
    for (e = list_begin(s_page_table); e != list_end(s_page_table);
         e = list_next(e))
    {
      struct s_page_table_entry *spte = list_entry(e, struct s_page_table_entry, elem);
      if (spte->file == thread_current()->fd[fd])
      {
        load_page_of_mmap(spte);
      }
    }
    file_close(thread_current()->fd[fd]);
    // release_file_lock();
    thread_current()->fd[fd] = NULL;
    break;
  }
  case SYS_MMAP:
  {
    check_pointer((int *)f->esp + 1);
    int fd = *((int *)f->esp + 1);
    check_pointer(((int *)f->esp + 2));
    const char *upage = (const char *)(*((int *)f->esp + 2));
    if (fd == 0 || fd == 1)
      exit_special();
    if (pg_ofs(upage) != 0)
    {
      f->eax = -1;
      break;
    }
    // printf("mmap\n");
    if (!upage)
    {
      f->eax = -1;
      break;
    }
    struct file *fi = thread_current()->fd[fd];
    if (!fi)
    {
      f->eax = -1;
      break;
    }
    off_t length_of_file = file_length(fi);
    if (length_of_file <= 0)
    {
      f->eax = -1;
      break;
    }
    int num_of_page = length_of_file / PGSIZE + 1;
    int i;
    for (i = 0; i < num_of_page; i++)
    {
      void *this_page = upage + PGSIZE * i;
      if (get_s_pt_entry(this_page) != NULL)
      {
        f->eax = -1;
        return;
      }
    }

    //get current mapid
    thread_current()->map_id++;
    mapid_t id = thread_current()->map_id;
    off_t offset = 0;
    uint32_t read_bytes = length_of_file;

    while (read_bytes > 0)
    {
      size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
      size_t page_zero_bytes = PGSIZE - page_read_bytes;
      struct s_page_table_entry *s_pt_entry = malloc(sizeof(struct s_page_table_entry));
      if (s_pt_entry == NULL)
      {
        f->eax = -1;
        return;
      }
      s_pt_entry->file = fi;
      s_pt_entry->offset = offset;
      s_pt_entry->upage = upage;
      s_pt_entry->read_bytes = page_read_bytes;
      s_pt_entry->zero_bytes = page_zero_bytes;
      s_pt_entry->writable = true;
      s_pt_entry->type = PAGE_MMAP;
      s_pt_entry->is_in_memory = false;
      struct list *s_page_table = &thread_current()->s_page_table;
      struct lock *s_page_table_lock = &thread_current()->s_page_table_lock;

      // add entry
      lock_acquire(s_page_table_lock);
      list_push_back(s_page_table, &s_pt_entry->elem);
      lock_release(s_page_table_lock);
      add_to_mmap_file_list(s_pt_entry);
      read_bytes -= page_read_bytes;
      upage += PGSIZE;
      offset += PGSIZE;
    }

    f->eax = id;
    break;
  }
  case SYS_MUNMAP:
  {
    check_pointer((int *)f->esp + 1);
    int mapping = *((int *)f->esp + 1);
    if (mapping <= 0)
      break;
    munmap(mapping);
    break;
  }
  default:
    printf("system call other!\n");
  }
}
